/**
 * @module effects
 * @description
 * 这个模块包含了所有用于对 `TerrainSchematic` 实例施加视觉或结构“效果”的函数。
 * 这些函数通常会修改蓝图中的方块，以实现如腐朽、平滑、线框化等外观上的变化。
 */

import type TerrainSchematic from "../schematic";
import { SchematicBlock } from "../types";
import { normalizeAndUpdateBounds } from "./geometry";
import {
  getOrCreatePaletteIndex,
  getPalette,
  optimizePalette,
  replaceAllBlocks,
} from "./palette";

/**
 * 对结构进行风化侵蚀，随机移除一定比例的方块。
 * @param percentage 要移除的方块比例，一个0到1之间的小数。
 * @param applyGravity 是否模拟重力，移除因侵蚀而悬空的方块。默认为 `true`。
 * @returns `this`，允许链式调用。
 */
export function decay(
  schematic: TerrainSchematic,
  percentage: number,
  applyGravity = true
): TerrainSchematic {
  if (percentage <= 0 || percentage > 1) {
    return schematic; // 无效比例，不执行操作
  }

  // 1. 随机移除方块
  const blocksToKeep = Math.floor(
    schematic.schematicBlocks.length * (1 - percentage)
  );
  // 通过随机排序并截取前N个来实现随机移除
  schematic.schematicBlocks.sort(() => Math.random() - 0.5);
  schematic.schematicBlocks = schematic.schematicBlocks.slice(0, blocksToKeep);

  // 2. 模拟重力
  if (applyGravity && schematic.schematicBlocks.length > 0) {
    // 寻找结构中的最低y坐标作为地面
    const minY = schematic.schematicBlocks.reduce(
      (min: number, b: SchematicBlock) => (b.y < min ? b.y : min),
      schematic.schematicBlocks[0].y
    );

    let stable = false;
    while (!stable) {
      const initialCount = schematic.schematicBlocks.length;
      const blockSet = new Set(
        schematic.schematicBlocks.map(
          (b: SchematicBlock) => `${b.x},${b.y},${b.z}`
        )
      );

      schematic.schematicBlocks = schematic.schematicBlocks.filter(
        (block: SchematicBlock) => {
          // 在最低层的方块永远被认为是稳定的
          if (block.y === minY) {
            return true;
          }
          // 检查下方是否有方块支撑
          return blockSet.has(`${block.x},${block.y - 1},${block.z}`);
        }
      );

      // 如果没有方块被移除，说明结构已经稳定
      if (schematic.schematicBlocks.length === initialCount) {
        stable = true;
      }
    }
  }

  // 3. 清理调色板
  optimizePalette(schematic);
  return schematic;
}

/**
 * 根据方块在指定轴向上的位置，使用一组新的方块来替换它，形成渐变效果。
 * @param axis 'x', 'y', 或 'z'，指定应用渐变的轴。
 * @param voxelIds 一个方块ID数组，定义了从最小坐标到最大坐标的渐变顺序。
 * @param dither 可选参数。启用抖动效果，使过渡更平滑。默认启用。
 * @returns `this`，允许链式调用。
 */
export function gradientReplace(
  schematic: TerrainSchematic,
  axis: "x" | "y" | "z",
  voxelIds: voxelId[],
  dither = true
): TerrainSchematic {
  if (voxelIds.length < 2 || schematic.schematicBlocks.length === 0) {
    return schematic; // 渐变需要至少两种方块
  }

  // 1. 获取坐标范围
  const coords = schematic.schematicBlocks.map((b: SchematicBlock) => b[axis]);
  const minCoord = Math.min(...coords);
  const maxCoord = Math.max(...coords);
  const range = maxCoord - minCoord;

  // 如果范围为0，所有方块都在同一层，只应用渐变的第一个方块
  if (range === 0) {
    return replaceAllBlocks(schematic, voxelIds[0]);
  }

  // 2. 准备新的调色板条目
  const newPaletteEntries: { id: voxelId; rot: voxelRotation }[] = voxelIds.map(
    (id) => ({ id, rot: 0 })
  );
  const newPaletteIndices = newPaletteEntries.map((entry) =>
    getOrCreatePaletteIndex(schematic, entry.id, entry.rot)
  );

  // 3. 应用渐变
  if (dither) {
    // 抖动逻辑
    for (const block of schematic.schematicBlocks) {
      const coord = block[axis];
      const normalizedPos = (coord - minCoord) / range;

      // 计算精确的浮点索引
      const floatIndex = normalizedPos * (voxelIds.length - 1);
      const lowerIndex = Math.floor(floatIndex);
      const upperIndex = Math.ceil(floatIndex);

      if (lowerIndex === upperIndex) {
        block.p = newPaletteIndices[lowerIndex];
      } else {
        // 使用随机性来决定选择哪个索引
        const threshold = floatIndex - lowerIndex;
        block.p =
          Math.random() < threshold
            ? newPaletteIndices[upperIndex]
            : newPaletteIndices[lowerIndex];
      }
    }
  } else {
    // 非抖动逻辑
    const step = range / (voxelIds.length - 1);
    for (const block of schematic.schematicBlocks) {
      const coord = block[axis];
      const index = Math.round((coord - minCoord) / step);
      block.p = newPaletteIndices[index];
    }
  }

  // 4. 清理调色板
  optimizePalette(schematic);
  return schematic;
}

/**
 * 在一个基础地形的表面上，根据一定的密度和规则，随机“盖印”另一个小型的结构。
 * @param stamp 要用作印章的`TerrainSchematic`实例。
 * @param options 配置参数对象。
 * @returns `this`，允许链式调用。
 */
export function cloneToSurface(
  schematic: TerrainSchematic,
  stamp: TerrainSchematic,
  options: {
    density?: number;
    alignToSurface?: boolean;
    randomYRotation?: boolean;
    allowedSurfaceNormals?: GameVector3[];
  } = {}
): TerrainSchematic {
  const {
    density = 0.1,
    alignToSurface = false,
    randomYRotation = false,
    allowedSurfaceNormals = [
      new GameVector3(0, 1, 0), // Up
    ],
  } = options;

  if (stamp.schematicBlocks.length === 0) {
    return schematic;
  }

  const baseBlockSet = new Set(
    schematic.schematicBlocks.map((b) => `${b.x},${b.y},${b.z}`)
  );
  const surfaceBlocks: { block: SchematicBlock; normal: GameVector3 }[] = [];

  // 1. 找到所有符合条件的表面方块
  for (const block of schematic.schematicBlocks) {
    const neighbors = [
      { normal: new GameVector3(0, 1, 0), x: 0, y: 1, z: 0 }, // Top
      { normal: new GameVector3(0, -1, 0), x: 0, y: -1, z: 0 }, // Bottom
      { normal: new GameVector3(1, 0, 0), x: 1, y: 0, z: 0 }, // Right
      { normal: new GameVector3(-1, 0, 0), x: -1, y: 0, z: 0 }, // Left
      { normal: new GameVector3(0, 0, 1), x: 0, y: 0, z: 1 }, // Front
      { normal: new GameVector3(0, 0, -1), x: 0, y: 0, z: -1 }, // Back
    ];

    for (const neighbor of neighbors) {
      const checkPos = `${block.x + neighbor.x},${block.y + neighbor.y},${
        block.z + neighbor.z
      }`;
      if (!baseBlockSet.has(checkPos)) {
        // 这是一个表面
        const normal = neighbor.normal;
        if (
          allowedSurfaceNormals.some((allowedNormal) =>
            allowedNormal.equals(normal)
          )
        ) {
          surfaceBlocks.push({ block, normal });
        }
        break; // 找到一个表面就够了，避免重复添加
      }
    }
  }

  // 2. 根据密度随机选择表面方块并应用印章
  const blocksToStamp = surfaceBlocks
    .sort(() => Math.random() - 0.5)
    .slice(0, Math.floor(surfaceBlocks.length * density));

  const stampBlocks = stamp.schematicBlocks;
  const newBlocks: SchematicBlock[] = [];

  // 将印章的调色板合并到主调色板
  const stampPalette = getPalette(stamp);
  const paletteMap: number[] = stampPalette.map((pal) => {
    return getOrCreatePaletteIndex(schematic, pal.id, pal.rot);
  });

  for (const { block: surfaceBlock, normal } of blocksToStamp) {
    let currentStampBlocks = stamp.schematicBlocks.map((b) => ({ ...b })); // 克隆印章方块以进行修改

    const tempVec = { x: 0, y: 0, z: 0 };

    // 1. 如果需要，将印章与表面法线对齐
    if (alignToSurface && !normal.equals(new GameVector3(0, 1, 0))) {
      // 为了简单起见，我们处理六个轴对齐的法线方向
      // 目标是将印章的局部 "上" 方向 (0, 1, 0) 旋转以匹配 'normal'
      if (normal.y === -1) {
        // 面向下方: 绕X轴旋转180度
        currentStampBlocks.forEach((b) => {
          b.y = -b.y;
          b.z = -b.z;
        });
      } else if (normal.x === 1) {
        // 面向右方 (+X): 绕Z轴旋转-90度
        currentStampBlocks.forEach((b) => {
          tempVec.x = b.x;
          tempVec.y = b.y;
          b.x = tempVec.y;
          b.y = -tempVec.x;
        });
      } else if (normal.x === -1) {
        // 面向左方 (-X): 绕Z轴旋转90度
        currentStampBlocks.forEach((b) => {
          tempVec.x = b.x;
          tempVec.y = b.y;
          b.x = -tempVec.y;
          b.y = tempVec.x;
        });
      } else if (normal.z === 1) {
        // 面向前方 (+Z): 绕X轴旋转90度
        currentStampBlocks.forEach((b) => {
          tempVec.y = b.y;
          tempVec.z = b.z;
          b.y = -tempVec.z;
          b.z = tempVec.y;
        });
      } else if (normal.z === -1) {
        // 面向后方 (-Z): 绕X轴旋转-90度
        currentStampBlocks.forEach((b) => {
          tempVec.y = b.y;
          tempVec.z = b.z;
          b.y = tempVec.z;
          b.z = -tempVec.y;
        });
      }
    }

    // 2. 如果需要，应用随机的Y轴旋转
    if (randomYRotation) {
      const angle = [0, 90, 180, 270][Math.floor(Math.random() * 4)];
      if (angle === 90) {
        // 绕Y轴旋转90度
        currentStampBlocks.forEach((b) => {
          tempVec.x = b.x;
          b.x = -b.z;
          b.z = tempVec.x;
        });
      } else if (angle === 180) {
        // 绕Y轴旋转180度
        currentStampBlocks.forEach((b) => {
          b.x = -b.x;
          b.z = -b.z;
        });
      } else if (angle === 270) {
        // 绕Y轴旋转270度
        currentStampBlocks.forEach((b) => {
          tempVec.x = b.x;
          b.x = b.z;
          b.z = -tempVec.x;
        });
      }
    }

    // 3. 放置方块
    // 假设印章的基准点(y=0)是其底部。
    // 我们将每个印章方块放置在相对于表面方块的位置，并根据法线方向进行偏移。
    for (const stampBlock of currentStampBlocks) {
      newBlocks.push({
        x: surfaceBlock.x + normal.x + stampBlock.x,
        y: surfaceBlock.y + normal.y + stampBlock.y,
        z: surfaceBlock.z + normal.z + stampBlock.z,
        p: paletteMap[stampBlock.p],
      });
    }
  }

  // 3. 将新方块添加到主结构中
  schematic.schematicBlocks.push(...newBlocks);
  normalizeAndUpdateBounds(schematic); // 需要重新计算边界和原点
  optimizePalette(schematic);

  return schematic;
}

export function smooth(
  schematic: TerrainSchematic,
  options: {
    iterations?: number;
    birthThreshold?: number;
    survivalThreshold?: number;
  } = {}
): TerrainSchematic {
  const { iterations = 1, birthThreshold = 5, survivalThreshold = 4 } = options;

  for (let i = 0; i < iterations; i++) {
    const nextGenerationBlocks = new Map<string, SchematicBlock>();
    const bounds = schematic.data.bounds;
    const size = (bounds.hi as GameVector3)
      .sub(bounds.lo as GameVector3)
      .add(new GameVector3(1, 1, 1));

    const currentBlockMap = new Map<string, SchematicBlock>(
      schematic.schematicBlocks.map((b) => [`${b.x},${b.y},${b.z}`, b])
    );
    const currentBlockSet = new Set(currentBlockMap.keys());

    for (let x = -1; x <= size.x; x++) {
      for (let y = -1; y <= size.y; y++) {
        for (let z = -1; z <= size.z; z++) {
          const neighborCount = countNeighbors(currentBlockSet, x, y, z);
          const posKey = `${x},${y},${z}`;
          const isAlive = currentBlockSet.has(posKey);

          if (isAlive) {
            if (neighborCount >= survivalThreshold) {
              const block = currentBlockMap.get(posKey);
              if (block) nextGenerationBlocks.set(posKey, { ...block });
            }
          } else {
            if (neighborCount >= birthThreshold) {
              const newPaletteIndex = getDominantNeighborPalette(
                currentBlockMap,
                x,
                y,
                z
              );
              nextGenerationBlocks.set(posKey, { x, y, z, p: newPaletteIndex });
            }
          }
        }
      }
    }
    schematic.schematicBlocks = Array.from(nextGenerationBlocks.values());
  }
  optimizePalette(schematic);
  return schematic;
}

function countNeighbors(
  blockSet: Set<string>,
  x: number,
  y: number,
  z: number
): number {
  let count = 0;
  for (let i = -1; i <= 1; i++) {
    for (let j = -1; j <= 1; j++) {
      for (let k = -1; k <= 1; k++) {
        if (i === 0 && j === 0 && k === 0) continue;
        if (blockSet.has(`${x + i},${y + j},${z + k}`)) {
          count++;
        }
      }
    }
  }
  return count;
}
function getDominantNeighborPalette(
  blockMap: Map<string, SchematicBlock>,
  x: number,
  y: number,
  z: number
): number {
  const paletteCounts = new Map<number, number>();
  for (let i = -1; i <= 1; i++) {
    for (let j = -1; j <= 1; j++) {
      for (let k = -1; k <= 1; k++) {
        if (i === 0 && j === 0 && k === 0) continue;
        const neighborKey = `${x + i},${y + j},${z + k}`;
        const neighborBlock = blockMap.get(neighborKey);
        if (neighborBlock) {
          paletteCounts.set(
            neighborBlock.p,
            (paletteCounts.get(neighborBlock.p) || 0) + 1
          );
        }
      }
    }
  }

  if (paletteCounts.size === 0) {
    return 0; // Fallback
  }

  let dominantPalette = -1;
  let maxCount = -1;
  for (const [p, count] of paletteCounts.entries()) {
    if (count > maxCount) {
      maxCount = count;
      dominantPalette = p;
    }
  }
  return dominantPalette;
}

export function sharpen(
  schematic: TerrainSchematic,
  options: {
    density?: number;
    maxHeight?: number;
  } = {}
): TerrainSchematic {
  const { density = 0.5, maxHeight = 10 } = options;
  const newBlocks: SchematicBlock[] = [];
  const blockSet = new Set(
    schematic.schematicBlocks.map((b) => `${b.x},${b.y},${b.z}`)
  );

  for (const block of schematic.schematicBlocks) {
    if (block.y >= maxHeight) continue;

    const aboveKey = `${block.x},${block.y + 1},${block.z}`;
    if (!blockSet.has(aboveKey) && Math.random() < density) {
      // 在上方添加一个同类型的方块
      newBlocks.push({ x: block.x, y: block.y + 1, z: block.z, p: block.p });
    }
  }

  schematic.schematicBlocks.push(...newBlocks);
  // No need to normalize, just update bounds if necessary, though sharpen only adds on top.
  // We can assume the bounds will only potentially expand upwards.
  if (newBlocks.length > 0) {
    const newMaxY = schematic.data.bounds.hi.y + 1;
    schematic.data.bounds.hi = new GameVector3(
      schematic.data.bounds.hi.x,
      newMaxY,
      schematic.data.bounds.hi.z
    );
  }

  return schematic;
}

/**
 * 将结构线框化，只保留其边缘和角落。
 * @param thickness 框架的厚度，默认为1。
 * @returns `this`，允许链式调用。
 */
export function wireFrame(
  schematic: TerrainSchematic,
  thickness = 1
): TerrainSchematic {
  if (thickness <= 0) {
    return schematic;
  }

  const blockSet = new Set<string>();
  for (const block of schematic.schematicBlocks) {
    blockSet.add(`${block.x},${block.y},${block.z}`);
  }

  const isExposedOnAxis = (
    block: { x: number; y: number; z: number },
    axis: "x" | "y" | "z"
  ): boolean => {
    let exposedPositive = false;
    for (let i = 1; i <= thickness; i++) {
      const checkPos = { ...block };
      checkPos[axis] += i;
      if (!blockSet.has(`${checkPos.x},${checkPos.y},${checkPos.z}`)) {
        exposedPositive = true;
        break;
      }
    }
    if (exposedPositive) {
      return true;
    }

    for (let i = 1; i <= thickness; i++) {
      const checkPos = { ...block };
      checkPos[axis] -= i;
      if (!blockSet.has(`${checkPos.x},${checkPos.y},${checkPos.z}`)) {
        return true;
      }
    }

    return false;
  };

  const newBlocks = schematic.schematicBlocks.filter(
    (block: SchematicBlock) => {
      const exposedAxes = (["x", "y", "z"] as const).filter((axis) =>
        isExposedOnAxis(block, axis)
      );
      return exposedAxes.length >= 2;
    }
  );

  schematic.schematicBlocks = newBlocks;
  optimizePalette(schematic);
  return schematic;
}

/**
 * 将结构的所有表面向外增厚指定的层数。
 * @param thickness 增厚的层数，默认为1。
 * @param voxelId 用于增厚的方块ID。
 * @param voxelRot 增厚方块的旋转，默认为0。
 * @returns `this`，允许链式调用。
 */
export function thicken(
  schematic: TerrainSchematic,
  thickness = 1,
  voxelId: voxelId,
  voxelRot: voxelRotation = 0
): TerrainSchematic {
  if (thickness <= 0) {
    return schematic;
  }

  const newBlocks: SchematicBlock[] = [];
  const blockSet = new Set(
    schematic.schematicBlocks.map((b) => `${b.x},${b.y},${b.z}`)
  );
  const newPaletteIndex = getOrCreatePaletteIndex(schematic, voxelId, voxelRot);

  for (let i = 0; i < thickness; i++) {
    const currentSurface = new Set<string>();
    for (const block of schematic.schematicBlocks) {
      const neighbors = [
        { x: 1, y: 0, z: 0 },
        { x: -1, y: 0, z: 0 },
        { x: 0, y: 1, z: 0 },
        { x: 0, y: -1, z: 0 },
        { x: 0, y: 0, z: 1 },
        { x: 0, y: 0, z: -1 },
      ];

      for (const n of neighbors) {
        const checkPos = `${block.x + n.x},${block.y + n.y},${block.z + n.z}`;
        if (!blockSet.has(checkPos) && !currentSurface.has(checkPos)) {
          currentSurface.add(checkPos);
          const [x, y, z] = checkPos.split(",").map(Number);
          newBlocks.push({ x, y, z, p: newPaletteIndex });
        }
      }
    }
    // Add new blocks to the set for the next iteration
    for (const newBlock of newBlocks) {
      blockSet.add(`${newBlock.x},${newBlock.y},${newBlock.z}`);
    }
  }

  schematic.schematicBlocks.push(...newBlocks);
  optimizePalette(schematic);
  // Re-normalize because thickening can change the origin
  normalizeAndUpdateBounds(schematic);
  return schematic;
}

/**
 * 将实心结构转换为空心外壳。
 * @param thickness 外壳的厚度。
 * @returns `this`，允许链式调用。
 */
export function shell(
  schematic: TerrainSchematic,
  thickness: number
): TerrainSchematic {
  if (thickness <= 0) {
    schematic.schematicBlocks = []; // 厚度为0或负数，清空所有方块
    normalizeAndUpdateBounds(schematic);
    return schematic;
  }

  const allBlocks = new Set(
    schematic.schematicBlocks.map((b) => `${b.x},${b.y},${b.z}`)
  );
  const shellBlocks = new Set<string>();

  let currentSurface = new Set<string>();
  for (const block of schematic.schematicBlocks) {
    const key = `${block.x},${block.y},${block.z}`;
    if (isSurfaceBlock(block, allBlocks)) {
      currentSurface.add(key);
    }
  }

  for (let i = 0; i < thickness; i++) {
    if (currentSurface.size === 0) {
      break; // 如果没有更多表面，提前退出
    }

    // 将当前表面添加到外壳，并准备寻找下一层表面
    const nextSurface = new Set<string>();
    for (const surfaceBlockKey of currentSurface) {
      shellBlocks.add(surfaceBlockKey);
    }

    // 找到所有与当前外壳层相邻的内部方块
    for (const surfaceBlockKey of currentSurface) {
      const [x, y, z] = surfaceBlockKey.split(",").map(Number);
      const neighbors = getNeighbors({ x, y, z });
      for (const neighbor of neighbors) {
        const neighborKey = `${neighbor.x},${neighbor.y},${neighbor.z}`;
        if (allBlocks.has(neighborKey) && !shellBlocks.has(neighborKey)) {
          nextSurface.add(neighborKey);
        }
      }
    }
    currentSurface = nextSurface;
  }

  // 从原始方块列表中过滤出属于外壳的方块
  const blockMap = new Map(
    schematic.schematicBlocks.map((b) => [`${b.x},${b.y},${b.z}`, b])
  );
  schematic.schematicBlocks = Array.from(shellBlocks).map(
    (key) => blockMap.get(key)!
  );
  optimizePalette(schematic);
  normalizeAndUpdateBounds(schematic);

  return schematic;
}

function isSurfaceBlock(
  block: { x: number; y: number; z: number },
  allBlocks: Set<string>
): boolean {
  return (
    !allBlocks.has(`${block.x + 1},${block.y},${block.z}`) ||
    !allBlocks.has(`${block.x - 1},${block.y},${block.z}`) ||
    !allBlocks.has(`${block.x},${block.y + 1},${block.z}`) ||
    !allBlocks.has(`${block.x},${block.y - 1},${block.z}`) ||
    !allBlocks.has(`${block.x},${block.y},${block.z + 1}`) ||
    !allBlocks.has(`${block.x},${block.y},${block.z - 1}`)
  );
}

function getNeighbors(block: { x: number; y: number; z: number }) {
  return [
    { x: block.x + 1, y: block.y, z: block.z },
    { x: block.x - 1, y: block.y, z: block.z },
    { x: block.x, y: block.y + 1, z: block.z },
    { x: block.x, y: block.y - 1, z: block.z },
    { x: block.x, y: block.y, z: block.z + 1 },
    { x: block.x, y: block.y, z: block.z - 1 },
  ];
}
